/**
 *  Copyright 2002 Quaseem
 *  Copyright 2004 Migisan
 *  Copyright 2008 Oest  
 *
 *  This file is part of the aochat package.
 *
 *  The aochat package is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  The aochat package is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with the aochat package; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package sk.sigp.aobot.client;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import sk.sigp.aobot.Colors;
import sk.sigp.aobot.Utils;
import sk.sigp.aobot.base.HaveListeners;
import sk.sigp.aobot.base.DetailedListener;
import sk.sigp.aobot.base.MessageListener;
import sk.sigp.aobot.client.core.MessageHandler;
import sk.sigp.aobot.client.core.MessageQueue;
import sk.sigp.aobot.client.core.Server;
import sk.sigp.aobot.client.core.User;
import sk.sigp.aobot.client.core.message.ChatMessage;
import sk.sigp.aobot.client.core.message.LoginMessage;
import sk.sigp.aobot.client.core.message.general.Message;
import sk.sigp.aobot.client.types.ChatGroupId;
import sk.sigp.aobot.client.types.GroupId;
import sk.sigp.aobot.client.types.Int;
import sk.sigp.aobot.client.types.Raw;
import sk.sigp.aobot.client.types.Text;
import sk.sigp.aobot.client.types.UserId;

/**
 * core chat functionality
 * @author Oest
 *
 */
public class BotCore extends HaveListeners<MessageListener> {

	private HaveListeners<DetailedListener> detailedListeners;
	
	public HaveListeners<DetailedListener> getDetailedListeners() {
		if (detailedListeners==null) {
			detailedListeners=new HaveListeners<DetailedListener>();
		}
		return detailedListeners;
	}
	
	protected void fireSendMessage(String user,String group) {
		fireEvent("messageRecieved", user, group);
	}
	
	protected void fireBuddyUpdate(String buddy,int status) {
		fireEvent("buddyUpdate", buddy, status);
	}
	
	protected void fireInvite(String userName) {
		fireEvent("invite", userName);
	}
	
	protected void fireGroupAdd(String group) {
		fireEvent("groupAdd", group);
	}
	
	protected void fireGroupRemove(String group) {
		fireEvent("groupRemove", group);
	}
	
	protected void fireSendMessage(String user) {
		fireEvent("messageRecieved", user);
	}
	
	protected void firePrivateMessage(String user,String message) {
		getDetailedListeners().fireEvent("privateMessage", user, message);
	}
	
	protected void fireGroupMessage(String group,String user,String message) {
		getDetailedListeners().fireEvent("groupMessage", group, user, message);
	}
	
	protected void firePrivateGroupMessage(String group,String user,String message) {
		getDetailedListeners().fireEvent("privateGroupMessage", group, user, message);
	}
	
	protected void fireVicinityMessage(String user,String message) {
		getDetailedListeners().fireEvent("vicinityMessage", user, message);
	}
	
	Map<Integer, User> users = new HashMap<Integer, User>();
	Map<Object, String> groups = new HashMap<Object, String>();

	public List<MessageListener> listeners;

	// login relevant data
	protected String username = "";
	protected String password = "";
	protected int botkey = 1;
	protected String botname = "";
	protected UserId botID;
	protected GroupId orgID;
	
	protected Server server = null;

	// comand-permission relevant data
	protected String ownername;

	protected MessageHandler handler;
	protected boolean online;
	protected boolean loggedOn;
	protected List<Message> inQueue;
	protected long lastRecieved;
	protected MessageQueue queue;
	
	private List<DelayedQueueObject> delayQueue;

	protected boolean debug;
	private boolean connected = false;

	public BotCore(String name, String login, String pwd, Server srv) 
		throws IOException {
		username = login;
		password = pwd;
		botname = name;
		queue = new MessageQueue(this);
		server = srv;
	}
	
	public void connect() throws IOException {
		online = false;
		handler = new MessageHandler(server.getServer(), server.getPort(), this);
		handler.connect();
		connected = true;
	}

	public synchronized void messageRecieved(Message msg) {
		User usr;
		lastRecieved = System.currentTimeMillis();

		switch (msg.getMessageType()) {
		case Message.M_S_LOGIN_SEED: {
			fireSendMessage("<i>Connected to " + server.getName()
					+ ". Logging in.</i>");
			String serverSeed = msg.getStringAt(0);
			send(new LoginMessage(username, password, serverSeed));
		}
			break;
		case Message.M_S_LOGIN_CHARACTERLIST: {
			UserId[] charIDs = (UserId[]) msg.getArrayAt(0);
			Text[] charNames = (Text[]) msg.getArrayAt(1);
			Int[] charOnline = (Int[]) msg.getArrayAt(3);
			{
				botID = null;
				Int on = null;
				for (int j = charNames.length - 1; j >= 0; j--)
					if (charNames[j].equals(botname)) {
						botID = charIDs[j];
						on = charOnline[j];
					}

				if (botID == null) {
					fireSendMessage("<i>Character '" + botname + "' not found.</i>");
				} else {
					if (on.equals(0)) {
						Message selectChar = new Message(
								Message.M_C_LOGIN_SELECT_CHARACTER);
						selectChar.setElementAt(0, botID);
						send(selectChar);
						usr = new User(this, botname, botID);
						usr.setBot(true);
						orgID = new GroupId((long) usr.organization_id);
					} else
						fireSendMessage("<i>Character '" + botname
								+ "' is already online.</i>");
				}
			}
		}
			break;

		case Message.M_S_LOGIN_RESULT_OK:
			fireSendMessage("<i>I'm online</i>");
			loggedOn = true;
			break;

		case Message.M_S_LOGIN_RESULT_ERROR:
			fireSendMessage("<i>Login error: " + msg.getStringAt(0) + "</i>");
			break;

		case Message.M_S_CLIENT_NAME:
		case Message.M_S_NAME_LOOKUP_RESULT: {
			UserId id = (UserId) msg.getElementAt(0);
			String name = msg.getStringAt(1);

			usr = new User(this, name, id);
			if (id.getIntData() != -1)
				users.put(id.getIntData(), usr);

			Object forDeletion = null;
			for (DelayedQueueObject mx : getDelayQueue()) {
				switch (mx.getType()) {

				// delayed add buddy
				case DelayedQueueObject.ADD_BUDDY: {
					String m = (String) mx.getData();
					if (m.equalsIgnoreCase(name) && id.getIntData() > -1) {
						// buddy add
						addBuddy(id);
						forDeletion = mx;
					} else if (id.getIntData()==-1) {
						fireSendMessage("Buddy couldnt be added cos he doesnt exist");
					}
				}
					break;

				// delayed private message
				case DelayedQueueObject.PRIVATE_MESSAGE: {
					ChatMessage m = (ChatMessage) mx.getData();
					if (m.potentialNick.equalsIgnoreCase(name)) {
						// we found delayed message for inquired name
						forDeletion = mx;
						if (id.getIntData() == -1)
							// inform client about send inquiry failed
							fireSendMessage("<i>unable to send to user, user wasnt found</i>");
						else
							// resend
							sendMessageToUser(m.text, name);
					}
				}
					break;
				}

				if (forDeletion != null)
					break;
			}
			if (forDeletion != null)
				getDelayQueue().remove(forDeletion);

			if (usr.nick.equalsIgnoreCase(ownername))
				usr.setOwner(true);
		}
			break;

		case Message.M_S_CHAT_NOTICE: {
			// Int i1 = (Int) msg.getElementAt(0);
			// Int i2 = (Int) msg.getElementAt(1);
			// Int i3 = (Int) msg.getElementAt(2);
			// String text = msg.getStringAt(3);
			fireSendMessage("Message delivery was delayed or reproducing delayed msg delivery");
		}
			break;

		case Message.M_S_MESSAGE_PRIVATE: {
			UserId id = (UserId) msg.getElementAt(0);
			String text = msg.getStringAt(1);
			Raw data = (Raw) msg.getElementAt(2);
			usr = new User(this, id);
			User found = users.get(id.getIntData());
			boolean x = (found.user_id.getIntData() != botID.getIntData());

			if (x)
				firePrivateMessage(found.nick, text);
			
			fireSendMessage("<font color=\""+Utils.chex(Colors.TELL_NAME)+"\">"
					+ (x ? "<a href=\"cmdt" + found.nick + "\">" + found.nick
							+ "</a>" : found.nick)
					+ "</font>: " + text);
			
			if (text.indexOf("user is currently AFK") == -1)
				process_message(new ChatMessage(usr, text, data));
		}
			break;

		case Message.M_S_MESSAGE_SYSTEM: {
			String text = msg.getStringAt(0);
			fireSendMessage("<i>[System] " + text+"</i>");

			int where = text.indexOf("is offline, message has been buffered");
			if (where != -1) {
				String name = text.substring(0, where - 1);
				usr = new User(this, name, -1);
				usr.setOnline(false);
			}

			where = text
					.indexOf("Your client is sending a high frequency of chat messages.");
			if (where != -1)
				queue.resend();
		}
			break;

		case Message.M_S_BUDDY_ADDED: {
			UserId id = (UserId) msg.getElementAt(0);
			int online = msg.getIntAt(1);
			Raw buddyStatus = (Raw) msg.getElementAt(2);

			usr = new User(this, id);
			usr.setBuddy(buddyStatus.mydata[0] == 1);
			usr.setOnline(online == 1);
			fireBuddyUpdate(users.get(id.getIntData()).nick, online);
		}
			break;

		case Message.M_S_BUDDY_REMOVED: {
			UserId id = (UserId) msg.getElementAt(0);
			usr = new User(this, id);
			usr.setBuddy(false);
			usr.setOnline(false);
		}
			break;
			
		case Message.M_S_PRIVATEGROUP_CLIENT_JOINED: {
			// user of chat group is joining it scenario
			// ChatGroupId gid = (ChatGroupId) msg.getElementAt(0);
			// UserId id = (UserId) msg.getElementAt(1);
			// usr = new User(this, id);
			// usr.setInChatGroup(true);
		}
			break;

		case Message.M_S_PRIVATEGROUP_CLIENT_PARTED: {
			// user of chat group is leaving it scenario
			// ChatGroupId gid = (ChatGroupId) msg.getElementAt(0);
			// UserId id = (UserId) msg.getElementAt(1);
		}
			break;

		case Message.M_S_PRIVATEGROUP_INVITED: {
			ChatGroupId gid = (ChatGroupId) msg.getElementAt(0);
			User gx = users.get(gid.getIntData());
			if (gx == null) {
				fireSendMessage("sorry due to bug in this sw you cant recognize user whos wanting to invite you to priv. group");
			} else {
				fireInvite(gx.nick);
			}
		}
			break;

		case Message.M_S_PRIVATEGROUP_KICKED: {
			ChatGroupId gid = (ChatGroupId) msg.getElementAt(0);
			String name = groups.get(gid);
			groupRemove(gid);
			fireSendMessage("<i>You have been kicked from private group of "
					+ name+"</i>");
		}
			break;

		case Message.M_S_PRIVATEGROUP_PART: {
			ChatGroupId gid = (ChatGroupId) msg.getElementAt(0);
			String name = groups.get(gid);
			groupRemove(gid);
			fireSendMessage("<i>You have left private group of " + name+"</i>");
		}
		
		case Message.M_S_PRIVATEGROUP_MESSAGE: {
			ChatGroupId cgid = (ChatGroupId) msg.getElementAt(0);
			UserId id = (UserId) msg.getElementAt(1);
			String text = msg.getStringAt(2);
			Raw data = (Raw) msg.getElementAt(3);
			usr = new User(this, id);

			String gfound = users.get(cgid.getIntData()).nick;
			User found = users.get(id.getIntData());
			boolean x = (found.user_id.getIntData() != botID.getIntData());

			if (x)
				firePrivateGroupMessage(gfound, found.nick, text);
			
			String xx = "<b><font color=\""+Utils.chex(Colors.PGROUP_NAME)+"\">"
					+ "<a href=\"cmdg"
					+ gfound
					+ "\">"
					+ gfound
					+ "</a>"
					+ "</font></b>"
					+ ", <font color=\""+Utils.chex(Colors.PGROUP_USERNAME)+"\">"
					+ (x ? "<a href=\"cmdt" + found.nick + "\">" + found.nick
							+ "</a>" : found.nick) + "</font>: " + text;
			
			fireSendMessage(xx,gfound);

			process_message(new ChatMessage(usr, cgid, text, data));
		}
			break;

		case Message.M_S_GROUP_JOIN: {
			GroupId cgid = (GroupId) msg.getElementAt(0);
			Text text = (Text) msg.getElementAt(1);
			String x = text.getStringData();
			if (text.getStringData().indexOf("name unknown") != -1)
				x = "ORG";
			groups.put(cgid, x);
			fireGroupAdd(x);
		}
			break;

		case Message.M_S_MESSAGE_ANON_VICINITY: {
			Text text = (Text) msg.getElementAt(1);
			fireSendMessage("<font color=\""+Utils.chex(Colors.VICINITY_ANON)+"\">" + text.getStringData()
					+ "</font>");
		}
			break;

		case Message.M_S_MESSAGE_VICINITY: {
			UserId id = (UserId) msg.getElementAt(0);
			Text text = (Text) msg.getElementAt(1);
			User found = users.get(id.getIntData());
			
			boolean x = (found.user_id.getIntData() != botID.getIntData());
			
			if (x)
				fireVicinityMessage(found.nick, text.getStringData());
			
			String xx = "<font color=\""+Utils.chex(Colors.VICINITY_USER)+"\">"
					+ (found == null ? id.getLongData() : found.nick)
					+ "</font>: " + "<font color=\""+Utils.chex(Colors.VICINITY)+"\">"
					+ text.getStringData() + "</font>";
			if (listeners != null)
				fireSendMessage(xx);
			fireSendMessage(xx);
		}
			break;

		case Message.M_S_GROUP_MESSAGE: {
		    try{
			GroupId gid = (GroupId) msg.getElementAt(0);
			UserId id = (UserId) msg.getElementAt(1);

			String text = msg.getStringAt(2);
			User found = users.get(id.getIntData());
			String gfound = groups.get(gid);			
			boolean x = (found.user_id.getIntData() != botID.getIntData());
			
			if (x)
				fireGroupMessage(gfound, found.nick, text);
			
			String xx = "<b><font color=\""+Utils.chex(Colors.GROUP_NAME)+"\">"
					+ "<a href=\"cmdg"
					+ gfound
					+ "\">"
					+ gfound
					+ "</a>"
					+ "</font></b>"
					+ ", <font color=\""+Utils.chex(Colors.GROUP_USERNAME)+"\">"
					+ (x ? "<a href=\"cmdt" + found.nick + "\">" + found.nick
							+ "</a>" : found.nick) + "</font>: " + text;
			fireSendMessage(xx,gfound);
			Raw data = (Raw) msg.getElementAt(3);
			usr = new User(this, id);
			if (gid.equals(orgID))
				process_message(new ChatMessage(usr, gid, text, data));
			else {
				// listen to news channels?
				process_message(new ChatMessage(usr, gid, text, data));
			}
		    } catch (NullPointerException e) {
			//just stuopid error workarround, have to will
			//to validate all those shit for null 
		    }
		}
			break;

		default:
//			System.out.println("unexpected message type ("
//					+ msg.getMessageType() + ") received: " + msg.toString());
			getInQueue().clear();
		}
	}

	/**
	 * will join private group
	 * (invite is needed to do so)
	 * @param name
	 */
	public void joinPrivateGroup(String name) {
		fireSendMessage("<i>You have joined private chat of " + name+"</i>");
		User gx = findUser(name);
		ChatGroupId cid = new ChatGroupId(gx.user_id.getIntData());
		Message lookup = new Message(Message.M_C_PRIVATEGROUP_JOIN);
		lookup.setElementAt(0, cid);
		groups.put(cid, name);
		send(lookup);
		fireGroupAdd(name);
	}

	/**
	 * will leave private group
	 * @param name
	 */
	public void leavePrivateGroup(String name) {
		if (!isConnected()) 
			return;
		User gx = findUser(name);
		if (gx==null) {
			fireSendMessage("<i>Private group you want to leave doesnt exist</i>");
			return;
		}
		ChatGroupId cid = new ChatGroupId(gx.user_id.getIntData());
		Message lookup = new Message(Message.M_C_PRIVATEGROUP_PART);
		lookup.setElementAt(0, cid);
		send(lookup);
	}
	
	protected void process_message(ChatMessage msg) {
		StringTokenizer st = new StringTokenizer(msg.text);
		if (st.countTokens() < 1)
			return;
		if (msg.usr.nick.equalsIgnoreCase(botname))
			return; 
		String param[] = new String[st.countTokens()];
		for (int i = 0; st.hasMoreTokens(); i++)
			param[i] = st.nextToken();
	}

	public void nameLookup(String name) {
		Message lookup = new Message(Message.M_C_NAME_LOOKUP);
		lookup.setElementAt(0, new Text(name.toLowerCase()));
		send(lookup);
	}

	/**
	 * will remove group you have left from client cache
	 * 
	 * @param g
	 */
	public void groupRemove(ChatGroupId g) {
		String name = groups.get(g);
		groups.remove(g);
		fireGroupRemove(name);
	}

	public void ping() {
		Message lookup = new Message(Message.M_C_PING);
		lookup.setElementAt(0, new Text("Oest's Brave Bot"));
		send(lookup);
	}

	/**
	 * the buddy list is split in real buddies and pending buddies. in order to
	 * get the online status of a user it is neccessary to make him a pending
	 * buddy
	 */
	public void addBuddy(String uname) {
		if (!isConnected()) 
			return;
		User gx = findUser(uname);
		if (gx == null) {
			// if user wasnt found yet perform lookup first
			getDelayQueue().add(
					new DelayedQueueObject(DelayedQueueObject.ADD_BUDDY, uname));
			nameLookup(uname);
		} else
			// if user was already found, just add him
			addBuddy(gx.user_id);
	}

	/**
	 * will try to add user into buddy list by id
	 * @param u
	 */
	public void addBuddy(UserId u) {
		Message msg = new Message(Message.M_C_BUDDY_ADD);

		Raw onlineStatus = new Raw(new byte[] { (byte) 1 });		
		msg.setElementAt(0, u);
		msg.setElementAt(1, onlineStatus);
		send(msg);
		fireSendMessage("<i>New buddy was added</i>");
	}

	/**
	 * will try to remove buddy by name
	 * @param name
	 */
	public void removeBuddy(String name) {
		if (!isConnected()) 
			return;
		User gx = null;
		for (Integer gId : users.keySet()) {
			String g = users.get(gId).nick;
			if (g.equalsIgnoreCase(name)) {
				gx = users.get(gId);
				break;
			}
		}
		if (gx == null) {
			fireSendMessage("<i>Buddy couldnt be removed cos he wasnt found</i>");
			return;
		}
		Message msg = new Message(Message.M_C_BUDDY_REMOVE);
		msg.setElementAt(0, gx.user_id);
		fireSendMessage("<i>Buddy " + name + " was removed</i>");
		send(msg);
		fireBuddyUpdate(name, -1);
	}

	/**
	 * Should only be called by the MessageQueue or for messages that are not
	 * regulated.
	 */
	public void send(Message msg) {
		try {
			handler.send(msg);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void sendMessage(Message msg, boolean priority) {
		queue.add(msg, priority);
	}

	public void sendMessage(ChatMessage msg, boolean priority) {
		Message[] msgs = msg.toMessages();
		for (int i = 0; i < msgs.length; i++)
			queue.add(msgs[i], priority);
	}

	/**
	 * will send message to either group or private group
	 * 
	 * @param text
	 * @param chatGroup
	 */
	public void sendMessageToGroup(String text, String chatGroup) {
		Object gx = null;
		for (Object gId : groups.keySet()) {
			String g = groups.get(gId);
			if (g.equalsIgnoreCase(chatGroup)) {
				gx = gId;
				break;
			}
		}
		if (gx == null) {
			fireSendMessage("<i>unable to send message, group wasnt found</i>");
		} else {			
			if (gx instanceof GroupId) {
				ChatMessage x = new ChatMessage(null, (GroupId) gx, text,
						(Raw) null);
				sendMessage(x, false);
			} else if (gx instanceof ChatGroupId) {
				ChatMessage x = new ChatMessage(null, (ChatGroupId) gx, text,
						(Raw) null);
				sendMessage(x, false);
			}
		}
	}

	/**
	 * will find user
	 * 
	 * @param name
	 * @return
	 */
	private User findUser(String name) {
		User gx = null;
		for (Integer gId : users.keySet()) {
			String g = users.get(gId).nick;
			if (g.equalsIgnoreCase(name)) {
				gx = users.get(gId);
				break;
			}
		}
		return gx;
	}

	public void sendMessageToUser(String text, String user) {
		// 12886579201
		User gx = findUser(user);
		if (gx == null) {
			// try to identify user before sending message first..
			ChatMessage x = new ChatMessage(gx, text, (Raw) null);
			x.type = ChatMessage.MESSAGE_TYPE_PRIVATE;
			x.potentialNick = user;
			getDelayQueue().add(
					new DelayedQueueObject(DelayedQueueObject.PRIVATE_MESSAGE, x));
			// send namelookup
			nameLookup(user);
			return;
		} else {
			ChatMessage x = new ChatMessage(gx, text, (Raw) null);
			x.type = ChatMessage.MESSAGE_TYPE_PRIVATE;
			sendMessage(x, false);
		}
	}

	public ChatGroupId getChatgroupID() {
		return new ChatGroupId(this.botID.getIntData());
	}

	public List<Message> getInQueue() {
		if (inQueue == null) {
			inQueue = new ArrayList<Message>();
		}
		return inQueue;
	}

	public List<DelayedQueueObject> getDelayQueue() {
		if (delayQueue == null) {
			delayQueue = new ArrayList<DelayedQueueObject>();
		}
		return delayQueue;
	}
	
	public boolean isConnected() {
		return connected;
	}
}
